package lib;

import static util.Constant.FILE_ITEMS;
import static util.Constant.PATH_DATA;
import static util.Constant.STR_FILE_EXPECTED;
import static util.Constant.STR_INCOMPATIBLE_TYPE;
import static util.Constant.STR_NAME_NULL;
import static util.Tool.getMaxID;
import io.InOutput;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import util.Comparator;
import util.FileOperator;
import util.Tool;
import base.Tag;
import base.items.Item;

/**
 * The EvetManager {@code class}. Manage the events
 * 
 * @author Peter NGO, Guillaume POUSSEL and Mathieu RUL
 * @version 1.0
 */
public class ItemManager {

    /**
     * The {@code EventManager} instance.
     */
    private static ItemManager managerInstance;

    /**
     * Get an instance of {@code EventManager}.
     * 
     * @return an instance of {@code EventManager}.
     */
    public static ItemManager getInstance() {
        if (managerInstance == null) {
            managerInstance = new ItemManager();
        }
        return managerInstance;
    }

    /**
     * The {@code Hashtable} containing the Event object.
     */
    private Hashtable<Integer, Object> itemTable;

    /**
     * The {@code String} containing the path of Event data.
     */
    private String path = PATH_DATA + FILE_ITEMS;

    /**
     * Constructor of the {@code class}. Create the user manager.
     */
    private ItemManager() {
        initialize();
        try {
            load();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Add a tag to an Event
     * 
     * @param tagID Tag ID
     * @param eventID Event ID
     * 
     * @return <code>true</code> si l'ajout a fonctionné, <code>false</code>
     *         sinon.
     */
    public boolean addTag(Integer eventID, Integer tagID) {
        boolean retour = ((Item) itemTable.get(eventID)).addTag(tagID);
        save();
        return retour;
    }

    /**
     * Add a tag to an Item
     * 
     * @param tag Tag
     * @param item Item
     * 
     * @return <code>true</code> si l'ajout a fonctionné, <code>false</code>
     *         sinon.
     */
    public boolean addTag(Item item, Tag tag) {
        return this.addTag(item.getID(), tag.getID());
    }

    /**
     * Create a new Event
     * 
     * @param type the item type
     * 
     * @return Integer the ID of created event
     * @throws Exception
     * 
     * @throws IllegalArgumentException
     */
    public Item create(String type) throws Exception {
        if (type == null || type.trim().isEmpty())
            throw new IllegalArgumentException(STR_NAME_NULL);
        Integer ID = getMaxID(itemTable) + 1;
        Class<?> classe = Class.forName("base.items." + Tool.toClassName(type));
        Constructor<?> constructeur;
        try {
            constructeur = classe.getConstructor(int.class);
        } catch (Exception e) {
            constructeur = classe.getConstructor(Integer.class);
        }
        Item item = ((Item) constructeur.newInstance(getMaxID(itemTable) + 1));

        itemTable.put(ID, item);
        save();
        return item;
    }

    public boolean export(Item item, String destName) throws IOException {
        if (item.getPath() == null)
            return false;
        File source = new File(item.getPath());

        File dest = new File(destName);
        if (dest.isDirectory() && dest.canWrite()) {
            FileOperator.copyFile(source, new File(dest.getAbsolutePath()+"/"+source.getName()));
            Hashtable<Integer, Object> table = new Hashtable<Integer, Object>();
            table.put(item.getID(), item);
            InOutput.write(table, dest.getAbsolutePath()+"/"+"item_"+item.getID()+".xml");
        } else
            return false;

        return true;

    }

    /**
     * 
     * 
     * @param ID the event ID
     */
    public Item get(Integer ID) {
        return (itemTable.get(ID) == null ? null : (Item) itemTable.get(ID));
    }

    public Set<Item> getAll() {
        Set<Item> items = new HashSet<Item>();
        for (Object obj : itemTable.values()) {
            items.add((Item) obj);
        }
        return items;
    }

    public Item importFromFile(String filename, String type) throws Exception {
        File file = new File(filename);
        if (!file.isFile())
            throw new IllegalArgumentException(STR_FILE_EXPECTED);
        String thisMT = FileOperator.getMimeType(filename);
        Item item = create(type);
        
        String[] allowed = item.allowedMimeTypes();
        if (allowed.length > 0 && null != thisMT ) {
            boolean ok = false;
            for (String mt : allowed) {
                if (mt.equals(thisMT))
                    ok = true;
            }
            if (!ok) {
                throw new IllegalArgumentException(STR_INCOMPATIBLE_TYPE);
            }
        }
        item.setTitle(file.getName());
        item.setPath(filename);
        save();
        return item;
    }

    /**
     * Initialize the instance.
     */
    public void initialize() {
        itemTable = new Hashtable<Integer, Object>();
    }

    /**
     * Load the database in item table.
     * 
     * @throws Exception
     */
    private void load() throws Exception {
        itemTable = InOutput.read(path);
    }

    /**
     * Remove an Item
     * 
     * @param ID the tag ID
     */
    public Item remove(Integer ID) {
        for (Integer cID : get(ID).getComments()) {
            CommentManager.getInstance().remove(cID);
        }
        Item retour = (Item) itemTable.remove(ID);
        save();
        return retour;
    }

    /**
     * Remove a tag to an Item
     * 
     * @param tagID Tag ID
     * @param itemID Event ID
     * 
     * @return <code>true</code> si la suppression a fonctionné,
     *         <code>false</code> sinon.
     */
    public boolean removeTag(Integer itemID, Integer tagID) {
        boolean retour = ((Item) itemTable.get(itemID)).removeTag(tagID);
        save();
        return retour;
    }

    /**
     * Remove a tag to an Event
     * 
     * @param item Item
     * @param tag Tag
     * 
     * @return <code>true</code> si la suppression a fonctionné,
     *         <code>false</code> sinon.
     */
    public boolean removeTag(Item item, Tag tag) {
        return this.removeTag(item.getID(), tag.getID());
    }

    /**
     * Save the item table in database.
     */
    public void save() {
        InOutput.write(itemTable, path);
    }

    /**
     * Search an item with his name
     * 
     * @param name the item name
     * @throws Exception
     */
    public Item[] searchByName(String name) throws Exception {
        for (Object tagObj : itemTable.values()) {
            Item item = (Item) tagObj;
            if (item.getTitle().equals(name))
                return new Item[] { item };
        }

        Set<Item> items = new HashSet<Item>();
        for (Object tagObj : itemTable.values()) {
            Item item = (Item) tagObj;
            if (Comparator.compare(item.getTitle(), name))
                items.add(item);
        }

        return items.toArray(new Item[items.size()]);
    }

    /**
     * Set the path.
     * 
     * @param path the path to set.
     */
    public void setPath(String path) {
        this.path = path;
    }
}
